# 一些需要注意的js输出问题整理
# 1、js运行之前,会把带有var和function关键字的事先声明,但不会赋值
alert(a) //function a(){alert(10)}
a();//执行alert(10)
var a=3;
function a(){
alert(10)
}
alert(a) //3
a=6;
a(); //a已经不是一个函数了报错
2
3
4
5
6
7
8
9
alert(a) //undefined
a(); // 报错 a 不是一个函数,函数表达式函数必须在表达式之后调用
var a=3;
var a=function(){
alert(10)
}
alert(a)
a=6;
a();
2
3
4
5
6
7
8
9
考点:第一变量和函数声明提前,第二函数声明优先于变量声明!
# 2、下面代码输出什么?
var a=0;
var c=0;
function aa(a){
alert(a)
var a=3
c = 10;
}
aa(5) // 5
alert(a) //这里的a未受到函数体内a变量覆盖
alert(c) // 10
2
3
4
5
6
7
8
9
10
var a=0;
function aa(a){
alert(a)
a=3
}
aa(5)
alert(a)
//5,0 在函数体内,执行alert(a)和a=3,修改的的并不是全局变量a,而是参数a
2
3
4
5
6
7
8
考点: 函数内部如果用var声明了相同名称的外部变量,函数将不再向上寻找;这里在函数体内,参数a的优先级高于变量a
# 3、严格模式下,以下程序的输出是什么:
(function(){
var a = b = 3;
})();
console.log("a defined? " + (typeof a !== 'undefined'));
console.log("b defined? " + (typeof b !== 'undefined'));
2
3
4
5
答案: a defined? false,b defined? true
理解这道题的核心在于如何理解var a = b = 3这句话,实际上这句话等于
var a;
b = 3;
a = 3;
2
3
这样子,实际上,b是声明在了全局变量中(编译器在预编译帮你声明了,然而在严格模式下是不行的) a是局部变量,所以在函数之外是没有定义的。
# 4、数组的filter,以下输出结果是什么:
var arr = [1,2,3];
arr[10] = 9;
arr.filter((item)=> {
return item === undefined
})
//答案
[]
2
3
4
5
6
7
8
解析: 是的,答案的确是[],不是[undefined x 7]。当数组中都是undefined时,数组就是空,或者说[empty x 7] === []。
# 5、写一个sum方法,可以实现以下两种调用方式
console.log(sum(2,3)) //5
console.log(sum(2)(3)) //5
2
答案:
//方法1
var sum = function(x,y) {
if(y === undefined) {
return function(y) {
return x + y;
}
}else {
return x + y;
}
}
//方法2
var sum = function(x){
if( arguments.length === 1) {
return function (y) {
return x + y;
}
} else {
console.log('here');
return arguments[0] + arguments[1];
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 6、+ - 运算符之惑:
console.log(1 + "2" + "2"); //"122"
console.log(1 + +"2" + "2"); //"32"
console.log(1 + -"1" + "2"); // "02"
console.log(+"1" + "1" + "2");//"112"
console.log( "A" - "B" + "2"); "NAN2"
console.log( "A" - "B" + 2); "NAN"
2
3
4
5
6
核心是以下几点:
- "- +"会隐式转换为Number类型
- 当"+"作为运算符出现在String类型前时,会认为需要字符串拼接,因此会隐式转换为String
- Number包含一个特殊的类型NaN,当对非数字进行Number转换时,会变为这个。
# 7、堆栈溢出之谜
下面的代码将会造成栈溢出,请问如何优化,不改变原有逻辑:
var list = readHugeList();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
nextListItem();
}
};
2
3
4
5
6
7
8
9
10
答案:
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
setTimeout(nextListItem,0)
}
};
2
3
4
5
6
7
8
首先必须搞清楚,堆栈溢出的原因:
原因是每次执行代码时,都会分配一定尺寸的栈空间(Windows系统中为1M),每次方法调用时都会在栈里储存一定信息(如参数、局部变量、返回值等等),这些信息再少也会占用一定空间,成千上万个此类空间累积起来,自然就超过线程的栈空间了。那么如何解决此类问题?
这里介绍两个思路解决此问题: 1、异步,2、闭包
显然,这里就是使用的第一种方法,闭包。为什么使用setTimeout就可以解决问题?我们看下与没用之前的差别。如果没有使用setTimeout,那么函数将在大数据前不断的回调,直到最后走到重点,最初的函数才运行结束,释放内存。 但是如果使用了setTimeout,我们知道它是异步的,即使设置了时间为0,它也允许先执行下面的内容,可以释放堆栈,从而避免堆栈溢出的问题。 换言之,加了setTimeout,nextListItem函数被压入事件队列,函数可以退出,因此每次会清空调用堆栈。
# 8、你真的懂对象(Object)的key吗?
var a={},
b={key:'b'},
c={key:'c'};
a[b]=123;
a[c]=456;
console.log(a[b]); //456
2
3
4
5
6
7
8
原因是什么呢? 这里了解ES6新的数据类型 map 的应该就会意识到了,没错,对象的 key 值是只允许 String 类型的,这也是为什么引入了 map 数据类型了。 好了,那如果把一个对象作为 key 值,就会调用 toString 方法了。
Object.prototype.toString(obj) 会得到什么呢?没错`[object Object]。 那所以
a[b] ==> a["[object Object"] = 123;
a[c] ==> a["[object Object"] = 456;
2
# 9、回文判断
请做一个回文判断的函数,判断是否是回文
这里主要考虑了一个健壮性的问题,多了一个正则来检测:
function check(str) {
str = str.replace(/\W/g,'').toLowerCase();
return str === str.split('').reverse().join('')
}
2
3
4
# 10、下面程序的输出结果是?
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
fn();
arguments[0]();
}
};
obj.method(fn, 1); // 10 2
2
3
4
5
6
7
8
9
10
11
12
13
14
这个我做错在第二个输出上,其实对this了解后就知道,第一个输出10应该是很显然的:虽然在程序执行时,使用了obj.method方法,让this指向了obj,但是真正的函数执行在函数体内部,也即当fn()执行的时候,this是指向window的,所以第一次执行结果是10
那么这里第二次执行arguments[0]为什么结果是2?
分析下在method(fn,1)执行时,经历了什么: 首先两个参数fn和1会被放入arguments中,在arguments中第一个参数就是我们传入的函数;接下来fn执行,此时this没有绑定因此指向window,输出10。 然而到了arguments0这一句,相当于把arguments[0]中的第一个参数拿来执行, 效果如下:
arguments[0]() //执行,等同于下面的
arguments.0() //当然这句话是不合法的,但是这样我们可以更清楚知道,this是指向arguments实例本身
2
arguments.length就是它本身的长度(arguments是一个类数组,具有length属性),因此输出2
# 11、try..catch程序的输出结果
(function () {
try {
throw new Error();
} catch (x) {
var x = 1, y = 2;
console.log(x);
}
console.log(x);
console.log(y);
})();
2
3
4
5
6
7
8
9
10
输出结果:
1
undefined
2
2
3
我们都知道var是在预编译阶段会有一个变量提升,这种类型很容易解决,但是当遇到在catch(x)中与已有变量重名的情况,一定要区分两者之间的关系。
用变量提升的方法,把程序重写并分析如下:
(function () {
var x,y; // 外部变量提升
try {
throw new Error();
} catch (x/* 内部的x */) {
x = 1; //内部的x,和上面声明的x不是一回事!!
y = 2; //内部没有声明,作用域链向上找,外面的y
console.log(x); //当然是1
}
console.log(x); //只声明,未赋值,undefined
console.log(y); //就是2了
})();
2
3
4
5
6
7
8
9
10
11
12
这样子就很清晰,之后注意预编译的过程,把变量和函数定义进行提升后,进行分析,会清楚很多
# 12、下面程序的输出
var x = 21;
var girl = function () {
console.log(x);
var x = 20;
};
girl (); // undefined
2
3
4
5
6
函数内部变量提升。 相当于:
var x = 21;
var girl = function() {
var x;
console.log(x); // undefined
x = 20;
}
}
2
3
4
5
6
7
# 13、 运算符考点: 下面程序输出是什么?
console.log(1 < 2 < 3);
console.log(3 > 2 > 1);
//true
//flase
2
3
4
5
核心在于 js 怎么去解析 < 和 > 运算符。 在JS中,这种运算符是从左向右运算的,所以 3>2>1 就被转换成了 true>1,而 true 的值是 1,接着比较 1>1 就返回 false 了。
# 14、parseInt (val, radix) :两个参数,val 值,radix 基数(就是多少进制转换)
["1", "2", "3"].map(parseInt)
// 答案:[1, NaN, NaN]
// 其实等于
['1', '2', '3'].map((item, index) => {
return parseInt(item, index)
})
2
3
4
5
6
- parseInt('1', 0); // radix为0时,使用默认的10进制。
- parseInt('2', 1); // radix值在2-36,无法解析,返回NaN
- parseInt('3', 2); // 基数为2,2进制数表示的数中,最大值小于3,无法解析,返回NaN
# 15、IEEE 754标准中的浮点数并不能精确地表达小数
var two = 0.2
var one = 0.1
var eight = 0.8
var six = 0.6
[two - one == one, eight - six == two]
//答案:[true, false]
2
3
4
5
6
// 巩固:
var two = 0.2;
var one = 0.1;
var eight = 0.8;
var six = 0.6;
(eight - six).toFixed(4) == two //true
2
3
4
5
6
# 16、下面代码的输出是什么?
const shape = {
radius: 10,
diameter() {
return this.radius * 2;
},
perimeter: () => 2 * Math.PI * this.radius
};
shape.diameter();
shape.perimeter();
2
3
4
5
6
7
8
9
10
答案: 20 and NaN
请注意,diameter是普通函数,而perimeter是箭头函数。
对于箭头函数,this关键字指向是它所在上下文(定义时的位置)的环境,与普通函数不同! 这意味着当我们调用perimeter时,它不是指向shape对象,而是指其定义时的环境(window)。没有值radius属性,返回undefined。
# 17、哪个选项是不正确的?
const bird = {
size: "small"
};
const mouse = {
name: "Mickey",
small: true
};
2
3
4
5
6
7
8
- A: mouse.bird.size
- B: mouse[bird.size]
- C: mouse[bird["size"]]
- D: All of them are valid
答案: A
在JavaScript中,所有对象键都是字符串(除了Symbol)。尽管有时我们可能不会给定字符串类型,但它们总是被转换为字符串。
JavaScript解释语句。当我们使用方括号表示法时,它会看到第一个左括号[,然后继续,直到找到右括号]。只有在那个时候,它才会对这个语句求值。
mouse [bird.size]:首先它会对bird.size求值,得到small。 mouse [“small”]返回true。
但是,使用点表示法,这不会发生。 mouse没有名为bird的键,这意味着mouse.bird是undefined。 然后,我们使用点符号来询问size:mouse.bird.size。 由于mouse.bird是undefined,我们实际上是在询问undefined.size。 这是无效的,并将抛出Cannot read property "size" of undefined。
# 18、下面代码的输出是什么?
let a = 3;
let b = new Number(3);
let c = 3;
console.log(a == b);
console.log(a === b);
console.log(b === c);
2
3
4
5
6
7
答案:true false false
new Number()是一个内置的函数构造函数。 虽然它看起来像一个数字,但它并不是一个真正的数字:它有一堆额外的功能,是一个对象。
当我们使用==运算符时,它只检查它是否具有相同的值。 他们都有3的值,所以它返回true。
译者注:== 会引发隐式类型转换,右侧的对象类型会自动拆箱为 Number 类型。
然而,当我们使用===操作符时,类型和值都需要相等,new Number() 不是一个数字,是一个对象类型。两者都返回 false。
# 19、下面代码的输出是什么?
class Chameleon {
static colorChange(newColor) {
this.newColor = newColor;
}
constructor({ newColor = "green" } = {}) {
this.newColor = newColor;
}
}
const freddie = new Chameleon({ newColor: "purple" });
freddie.colorChange("orange");
2
3
4
5
6
7
8
9
10
11
12
答案: TypeError
colorChange 方法是静态的。 静态方法仅在创建它们的构造函数中存在,并且不能传递给任何子级。 由于freddie是一个子级对象,函数不会传递,所以在 freddie 实例上不存在 freddie 方法:抛出 TypeError。
# 20、当我们这样做时会发生什么?
function bark() {
console.log("Woof!");
}
bark.animal = "dog";
2
3
4
5
- A: Nothing, this is totally fine!
- B: SyntaxError. You cannot add properties to a function this way.
- C: undefined
- D: ReferenceError
答案: A
这在JavaScript中是可能的,因为函数也是对象!(原始类型之外的所有东西都是对象)
函数是一种特殊类型的对象。您自己编写的代码并不是实际的函数。 该函数是具有属性的对象,此属性是可调用的。
# 21、下面代码的输出是什么?
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const member = new Person("Lydia", "Hallie");
Person.getFullName = () => this.firstName + this.lastName;
console.log(member.getFullName());
2
3
4
5
6
7
8
9
- A: TypeError
- B: SyntaxError
- C: Lydia Hallie
- D: undefined undefined
答案: A
您不能像使用常规对象那样向构造函数添加属性。 如果要一次向所有对象添加功能,则必须使用原型。 所以在这种情况下应该这样写:
Person.prototype.getFullName = function () {
return `${this.firstName} ${this.lastName}`;
}
2
3
这样会使 member.getFullName() 是可用的,为什么样做是对的? 假设我们将此方法添加到构造函数本身。 也许不是每个 Person 实例都需要这种方法。这会浪费大量内存空间,因为它们仍然具有该属性,这占用了每个实例的内存空间。 相反,如果我们只将它添加到原型中,我们只需将它放在内存中的一个位置,但它们都可以访问它!
# 22、所有对象都有原型吗?
不是的,础对象指原型链终点的对象。基础对象的原型是null。
# 23、下面代码的输出是什么?
function getPersonInfo(one, two, three) {
console.log(one);
console.log(two);
console.log(three);
}
const person = "Lydia";
const age = 21;
getPersonInfo`${person} is ${age} years old`;
2
3
4
5
6
7
8
9
10
- A: Lydia 21 ["", "is", "years old"]
- B: ["", "is", "years old"] Lydia 21
- C: Lydia ["", "is", "years old"] 21
答案: B
如果使用标记的模板字符串,则第一个参数的值始终是字符串值的数组。 其余参数获取传递到模板字符串中的表达式的值!
# 24、下面代码的输出是什么?
const sum = eval("10*10+5");
- A: 105
- B: "105"
- C: TypeError
- D: "10*10+5"
答案: A
eval会为字符串传递的代码求值。 如果它是一个表达式,就像在这种情况下一样,它会计算表达式。 表达式为10 * 10 + 5计算得到105。
# 25、下面代码的输出是什么?
const obj = { 1: "a", 2: "b", 3: "c" };
const set = new Set([1, 2, 3, 4, 5]);
obj.hasOwnProperty("1");
obj.hasOwnProperty(1);
set.has("1");
set.has(1);
2
3
4
5
6
7
- A: false true false true
- B: false true true true
- C: true true false true
- D: true true true true
答案: C
所有对象键(不包括Symbols)都会被存储为字符串,即使你没有给定字符串类型的键。 这就是为什么obj.hasOwnProperty('1')也返回true。
上面的说法不适用于Set。 在我们的Set中没有“1”:set.has('1')返回false。 它有数字类型1,set.has(1)返回true。
# 26、下面这些值哪些是假值?
0;
new Number(0);
("");
(" ");
new Boolean(false);
undefined;
2
3
4
5
6
- A: 0, '', undefined
- B: 0, new Number(0), '', new Boolean(false), undefined
- C: 0, '', new Boolean(false), undefined
- D: 所有都是假值
答案: A
JavaScript中只有6个假值:
- undefined
- null
- NaN
- 0
- '' (empty string)
- false
函数构造函数,如 new Number 和 new Boolean 都是真值。